#ifndef __baker_Intersect_h__
#define __baker_Intersect_h__

#include "Prerequire.h"
#include "Ray.h"
#include "Triangle.h"

namespace baker
{
    /** Template for intersection test between primitives (e.g. Ray, Plane, Triangle...etc).
    */
    template< typename T1
            , typename T2
            >
    class Intersect
    {
    };

    /** Ray3-Triangle interesct
    */
    template<>
    class Intersect< Ray3, Triangle >
    {
    public:

        Intersect< Ray3, Triangle >( void )
        {
            _reset();
        }

        inline void test( const Ray3 & ray
                        , const Triangle & tri
                        )
        {
            _reset();

            const Vec3 & a = tri.v[0];
            const Vec3 & b = tri.v[1];
            const Vec3 & c = tri.v[2];

            Vec3 n = tri.getNormal();

            real dotDN = dot( ray.dir, n );

            // the ray is parallel to the triangle
            if ( fabsf( dotDN ) < EPSILON ) return;

            mRayHitDist = dot( ( a - ray.orig ), n ) / dotDN;

            // hit at the -ve side of the ray, i.e. no hit
            if ( mRayHitDist < EPSILON ) return;

            gint8 k;

            // get the major axis
            Vec3 n2 = n * n;

            if ( n2.x() > n2.y() )
                if ( n2.x() > n2.z() ) k = 0; // X
                else k = 2; // Z
            else
                if ( n2.y() > n2.z() ) k = 1; // Y
                else k = 2; // Z

            // project to the major plane
            gint8 uv[4] = { 1, 2, 0, 1 };

            gint8 u = uv[k];
            gint8 v = uv[k+1];

            Vec3 H = ray.getPoint( mRayHitDist );

            real beta = (b[u] * H[v] - b[v] * H[u]) / (b[u] * c[v] - b[v] * c[u]);
            if ( beta < 0 ) return;

            real gamma = (c[v] * H[u] - c[u] * H[v]) / (b[u] * c[v] - b[v] * c[u]);
            if ( gamma < 0 ) return;

            if ( beta + gamma > 1 ) return;

            mBaryCoord[0] = beta;
            mBaryCoord[1] = gamma;

            mHit = 1;
        }

        inline bool getHit( void ) const
        {
            return ( mHit == 1 );
        }

        inline real getRayHitDist( void ) const
        {
            return mRayHitDist;
        }

        inline const Vec2 & getBrayCoord( void ) const
        {
            return mBaryCoord;
        }

    private:
        guint32 mHit;
        real mRayHitDist;
        Vec2 mBaryCoord;

        inline void _reset( void )
        {
            mHit        = 0;
            mRayHitDist = -1;
            mBaryCoord  = Vec2( -1, -1 );
        }
    };
}

#endif  // __baker_Intersect_h__
